#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <string.h>

#include "socket.hpp"

namespace Net
{

TCPSocket::TCPSocket(short port) :
	addr_(INADDR_ANY), port_(port)
{
	socket_ = ::socket(PF_INET, SOCK_STREAM, 0);
	if (socket_ < 0)
		throw Exception("socket");

	int sopt = 1;
	if (::setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &sopt, sizeof(sopt)) < 0)
		throw Exception("setsockopt");

	struct sockaddr_in addr;

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY;
	if (::bind(socket_, (struct sockaddr *)&addr, sizeof(addr)) < 0)
		throw Exception("bind");
}

TCPSocket::TCPSocket() :
	addr_(INADDR_ANY), port_(-1)
{
	socket_ = ::socket(PF_INET, SOCK_STREAM, 0);
	if (socket_ < 0)
		throw Exception("socket");
}

TCPSocket::TCPSocket(int socket, int addr, short port, AssignOnlyTag) :
	socket_(socket), addr_(addr), port_(port)
{}

TCPSocket::~TCPSocket()
{
	::close(socket_);
}

TCPSocket &TCPSocket::operator =(const TCPSocket &rhs)
{
	if (this != &rhs) {
		::close(socket_);
		socket_ = rhs.socket_;
	}
	return *this;
}

void TCPSocket::listen(int n)
{
	if (::listen(socket_, n) < 0)
		throw Exception("listen");
}

void TCPSocket::connect(const char *host, short port)
{
	struct hostent *phe;
	struct sockaddr_in addr;

	phe = ::gethostbyname(host);
	if (!phe)
		throw Exception("gethostbyname");
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	::memcpy(&addr.sin_addr, phe->h_addr_list[0], sizeof(addr.sin_addr));
	if (::connect(socket_, (struct sockaddr *)&addr, sizeof(addr)) < 0)
		throw Exception("connect");
}

const char *TCPSocket::addressString() const
{
	struct in_addr addr = {addr_};

	return inet_ntoa(addr);
}

void TCPSocket::send(const void *data, size_t size)
{
	long sizeSent = 0;
	const char *data_ = static_cast<const char *>(data);

	while (size > 0) {
		sizeSent = ::write(socket_, data_, size);
		if (sizeSent < 0)
			throw Exception("write");
		size -= sizeSent;
		data_ += sizeSent;
	}
}

void TCPSocket::recv(void *data, size_t size)
{
	long sizeRecvd = 0;
	char *data_ = static_cast<char *>(data);

	while (size > 0) {
		sizeRecvd = ::read(socket_, data_, size);
		if (sizeRecvd < 0)
			throw Exception("read");
		size -= sizeRecvd;
		data_ += sizeRecvd;
	}
}

bool TCPSocket::isReadable(int timeout)
{
	fd_set set;
	struct timeval tv;

	FD_ZERO(&set);
	FD_SET(socket_, &set);
	tv.tv_sec = timeout/1000;
	tv.tv_usec = timeout%1000;
	if (::select(socket_ + 1, &set, 0, 0, &tv) < 0)
		throw Exception("select");
	return FD_ISSET(socket_, &set);
}

bool TCPSocket::isError(int timeout)
{
	fd_set set;
	struct timeval tv;

	FD_ZERO(&set);
	FD_SET(socket_, &set);
	tv.tv_sec = timeout/1000;
	tv.tv_usec = timeout%1000;
	if (::select(socket_ + 1, 0, 0, &set, &tv) < 0)
		throw Exception("select");
	return FD_ISSET(socket_, &set);
}

TCPSocket *TCPSocket::accept()
{
	int accepted;
	
	struct sockaddr_in addr;
	socklen_t alen = sizeof(addr);

	if ((accepted = ::accept(socket_, (struct sockaddr *)&addr, &alen)) < 0) {
		throw Exception("accept");
	}
	return new TCPSocket(accepted, addr.sin_addr.s_addr, ntohs(addr.sin_port), AssignOnlyTag());
}

}
